﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using GMMGetTiles;
using System.IO;

namespace GMMControl
{
    /// <summary>
    /// This class provides a map control based on GoogleMapsMobile. 
    /// The aspect of the control is completely configurable via code:
    /// - it is resizable
    /// - the interactive controls contained can be shown or not
    /// Some properties, as the zoom level and the selected location, can be either
    /// modified interactively acting on the control or via code.
    /// </summary>
    public partial class GMMControl : UserControl
    {
        /// <summary>
        /// Defines the supported view modes for the control
        /// </summary>
        public enum ViewType
        {
            Map = 2,
            Satellite = 6
        }

        /// <summary>
        /// Used in Add[Row|Column] methods to indicate the growing side
        /// </summary>
        enum GrowingSide
        {
            BEFORE = -1,
            AFTER = 1,
        }
        
        readonly int borderOffset = 5;
        const string defaultImageDir = "Images"; //default cacheDirectory for tiles
        const int defaultZoomLevel = 12;
            

        string imageDir; //full path of image directory

        bool loaded; //true if the map is ready to be shown (consequently doesn't download tiles if it is false)

        GmmCommunicator.AcquiredTileEventHandler acquiringHandler; //reference to the acquiring Handler registered to GmmCommunicator.AcquiredTile event
        
        Image[][] tiles; //matrix of visible tiles
        Size tileSize; //size of each tiles (for GMM it is 64x64)
        Point topLeftOrigin; //coordinates (in px) of first (top-left) visible tile
        Rectangle window; //Space of visible tiles (Top, Left, Right and Bottom return respectively Top, Left, Right and Bottom visible tiles)
        int zoomLevel; //actual level of zoom
        int zoomMax; //max allowed level of zoom
        ViewType mode; //Indicate the typ of visualization (Map or Sat)

        GeoCoordinate selectedPos; //Coords of last selected location
        
        /// <summary>
        /// Sensible regions of control
        /// </summary>
        Rectangle zoomControlArea; //region for zoom bar
        Rectangle zoomIndicator; //Region of the zoom indicator inside the zoomControl
        Rectangle zoomIn, zoomOut; //region for zoomin and zoomout control
        Rectangle mapControlArea, satControlArea; //region of map and set controls
        Rectangle centrateControl; //region of centrate control

        bool showZoomControl; //determines the visible state of zoom controls
        bool showModeControl; //determines the visible state of view mode controls
        bool showCentrateControl; //determines the visible state of centering button
        
        /// <summary>
        /// Occurs when selected location changed.
        /// </summary>
        public event EventHandler SelectedLocationChanged; //event raised on changing of the selected location

        /// Variables for dragging
        Point prevDragPosition;
        Point clickPos;
        bool zoomIndicatorDragging; //Indicate wether a drag action is interesting the zoomIndicator

        /// <summary>
        /// Initializes a new instance of the <see cref="GMMControl"/> class.
        /// </summary>
        public GMMControl()
        {
            ImageDirectory = null; //produces a default initialization for imageDir
            window = new Rectangle();
            tileSize = new Size(64, 64);
            zoomMax = 17;
            zoomLevel = defaultZoomLevel;
            //mode = ViewType.Satellite; 
            mode = ViewType.Map;

            acquiringHandler = new GmmCommunicator.AcquiredTileEventHandler(GmmCommunicator_AcquiredTile); 
            GmmCommunicator.AcquiredTile += acquiringHandler; 
            
            zoomIn = new Rectangle(borderOffset, borderOffset, 14, 14);
            zoomOut = new Rectangle(borderOffset, 97, 14, 14);
            zoomControlArea = new Rectangle(zoomIn.Left, zoomIn.Bottom + 2, zoomIn.Width, zoomOut.Top - zoomIn.Bottom - 4);
            zoomIndicator = new Rectangle(
                zoomControlArea.X, 
                (int)(zoomControlArea.Y + (zoomControlArea.Height / (zoomMax + 1)) * (zoomMax - zoomLevel + 1) - 2),
                zoomControlArea.Width,
                5); //2 & 5 are respectively half and full height of indicator

            satControlArea = new Rectangle(0, borderOffset, 30, 14);
            mapControlArea = new Rectangle(0, borderOffset, 30, 14);

            centrateControl = new Rectangle(zoomIn.Right + borderOffset , borderOffset, 16, 16);

            showZoomControl = showModeControl = showCentrateControl = true;
            
            InitializeComponent();
        }

        /// <summary>
        /// Centers the map to the default point and loads the required tiles.
        /// After the costructor and until LoadMap is called no image is visualized into the control.
        /// The zoom level is reset to a default value.
        /// </summary>
        public void LoadMap()
        {
            //Coords of polo Fibonacci (used as default)
            double lat = 43.7211487834421;
            double lon = 10.4073143005371;

            LoadMap(lat, lon);
        }

        /// <summary>
        /// Centers the map to a specified point and loads the required tiles.
        /// After the costructor and until LoadMap is called no image is visualized into the control.
        /// The zoom level is reset to a default value, focusing on the coordinates.
        /// </summary>
        /// <param name="lat">Latitude to center in</param>
        /// <param name="lon">Longitude to center in</param>
        public void LoadMap(double lat, double lon)
        {
            zoomLevel = defaultZoomLevel;

            int vTiles = Height / tileSize.Height + 2;
            int hTiles = Width / tileSize.Width + 2;

            window = GMMGetTiles.GmmCommunicator.RemoteGetTiles(imageDir, zoomLevel, (int)mode, lat, lon, vTiles, hTiles);


            ///////////////Se cambiano le dimensioni l'array diventa inconsistente con la window
            //va ampliato l'array affinche contenga il precedente e abbia righe e colonne vuote per nuove immagini
            if (vTiles != window.Height + 1 || hTiles != window.Width + 1 || tiles == null)
            {
                tiles = new Image[vTiles][];
                for (int i = 0; i < vTiles; i++)
                {
                    tiles[i] = new Image[hTiles];
                    for (int j = 0; j < hTiles; j++)
                        tiles[i][j] = LocalGetTile(window.Left + j, window.Top + i);
                }
            }
            else
            {
                for (int i = 0; i < vTiles; i++)
                {
                    for (int j = 0; j < hTiles; j++)
                        tiles[i][j] = LocalGetTile(window.Left + j, window.Top + i);
                }
            }

            CenterMap(lat, lon);

            loaded = true;
        }

        #region InterThreadTileUpdatingCode
        /// <summary>
        /// Function to be invoked on acquiredTile, to handle the event.
        /// </summary>
        /// <param name="e">EventArgs associated to the receiving event</param>
        delegate void AcquiredTileDelegate(GmmCommunicator.AcquiredTileEventArgs e);
        /// <summary>
        /// Real handler of the acquiredTile event, it updates the content of the speficied tile
        /// </summary>
        /// <param name="e">EventArgs associated to the receiving event</param>
        void UpdateTile(GmmCommunicator.AcquiredTileEventArgs e)
        {
            //Cambia il rispettivo tile della matrice, caricando l'immagine da file
            //e invalida solo la parte da esso occcupata
            //Se ovviamente la tile non è più visibile ingora tutto
            int i = (int)(e.Tile.row - window.Top);
            int j = (int)(e.Tile.column - window.Left);
            if (i >= 0 && i <= window.Height && j >= 0 && j <= window.Width)
            {
                tiles[i][j] = LocalGetTile((int)e.Tile.column, (int)e.Tile.row);
            
                //Invalidate(new Rectangle(topLeftOrigin.X + j * tileSize.Width, topLeftOrigin.Y + i * tileSize.Height, tileSize.Width, tileSize.Height));
                //Update();
                Refresh();
            }
        }
        /// <summary>
        /// Handler to register on acquiredTile event.
        /// </summary>
        /// <param name="sender">Sender control</param>
        /// <param name="e">EventArgs associated to the event</param>
        void GmmCommunicator_AcquiredTile(object sender, GmmCommunicator.AcquiredTileEventArgs e)
        {
            AcquiredTileDelegate handler = new AcquiredTileDelegate(UpdateTile);
            this.Invoke(handler, e);
        }
        #endregion

        /// <summary>
        /// Raises the <see cref="E:System.Windows.Forms.Control.Resize"/> event.
        /// </summary>
        /// <param name="e">An <see cref="T:System.EventArgs"/> that contains the event data.</param>
        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);

            satControlArea.X = Width - borderOffset - satControlArea.Width - 2;
            mapControlArea.X = satControlArea.X - mapControlArea.Width - 2;
        }

        /// <summary>
        /// Centers the map to a specified location.
        /// </summary>
        /// <param name="lat">Latitude</param>
        /// <param name="lon">Longitude</param>
        private void CenterMap(double lat, double lon)
        {
            ////////////////////////////////////////
            //Vanno eguagliate le due coordinate alle coordinate del centro
            //
            //Le incognite diventano topLeftOrigin.X e topLeftOrigin.Y
            ////////////////////////////////////////
            //x = (int)((coords.column - window.Left) * tileSize.Width + topLeftOrigin.X);
            //y = (int)((coords.row - window.Top) * tileSize.Height + topLeftOrigin.Y);
            ////////////////////////////////////////
            TileCoordinate coords = GmmCommunicator.GeoCoord2TileCoord(lat, lon, zoomLevel);
            
            topLeftOrigin.X = Width / 2 - (int)((coords.column - window.Left) * tileSize.Width);
            topLeftOrigin.Y = Height / 2 - (int)((coords.row - window.Top) * tileSize.Height);

            RecalculateTiles(); ////////////////////////////////////
            Refresh();
        }

        /// <summary>
        /// Centers the map to the actual selected location.
        /// </summary>
        public void CentrateOnSelectedLoc()
        {
            if (selectedPos != null)
                CenterMap(selectedPos.lat, selectedPos.lon);
        }

        /// <summary>
        /// Loads a tile from the disk. If it isn't available a void (grey with a cross) tile is returned.
        /// </summary>
        /// <param name="x">The x identification of the tile</param>
        /// <param name="y">The y identification of the tile</param>
        /// <returns>The desired tile or a void tile</returns>
        private Image LocalGetTile(int x, int y)
        {
            string filename = string.Format("{0}{1:d2}_{2:d6}_{3:d6}.{4}", imageDir, zoomLevel+1, x, y, mode == ViewType.Map ? "PNG": "JPG");
            
            if (File.Exists(filename))
                return new Bitmap(filename);
            
            ////Soluzione Temporanea di debug
            Image tile = new Bitmap(tileSize.Width, tileSize.Height);

            using (Graphics g = Graphics.FromImage(tile))
            {
                g.Clear(Color.LightGray);

                using (Pen p = new Pen(Color.Black))
                {
                    g.DrawRectangle(p, 0, 0, 63, 63);
                    g.DrawLine(p, 0, 0, 64, 64);
                    g.DrawLine(p, 0, 64, 64, 0);
                }
                //g.DrawString(string.Format("[{0},{1}]", x, y), this.Font, new SolidBrush(Color.Black), 20, 20);
            }

            return tile;
        }

        #region PublicProperties
        /// <summary>
        /// Gets or sets the directory in which cache tiles.
        /// During the setting it is accepted an absolute path, started with '/', or a relative one.
        /// </summary>
        /// <value>The image directory.</value>
        public string ImageDirectory
        {
            get { return imageDir; }
            set
            {
                if (value == null || value.Trim().Equals("")) //Use default directory
                    imageDir = new StringBuilder(256)
                        .Append(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetModules()[0].FullyQualifiedName))
                        .Append(Path.DirectorySeparatorChar)
                        .Append(defaultImageDir)
                        .Append(Path.DirectorySeparatorChar)
                        .ToString();
                else if (value[0] == Path.DirectorySeparatorChar) //Use absolute path
                    imageDir = new StringBuilder(256)
                        .Append(value)
                        .Append(Path.DirectorySeparatorChar)
                        .ToString();
                else // Use relative path
                    imageDir = new StringBuilder(256)
                        .Append(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetModules()[0].FullyQualifiedName))
                        .Append(Path.DirectorySeparatorChar)
                        .Append(value)
                        .Append(Path.DirectorySeparatorChar)
                        .ToString();

                if (!Directory.Exists(imageDir))
                    Directory.CreateDirectory(imageDir);
            }
        }

        /// <summary>
        /// Gets or sets the visible state of the zoom controls
        /// </summary>
        /// <value><c>true</c> to show zoom controls; otherwise, <c>false</c>.</value>
        public bool ShowZoomControl { get; set; }

        /// <summary>
        /// Gets or sets the visible state of the mode controls
        /// </summary>
        /// <value><c>true</c> to show mode controls; otherwise, <c>false</c>.</value>
        public bool ShowModeControl { get; set; }

        /// <summary>
        /// Gets the actual selected location.
        /// </summary>
        /// <value>The selected location.</value>
        public GeoCoordinate SelectedLocation 
        { 
            get
            { 
                if (selectedPos != null)
                    return new GeoCoordinate(selectedPos.lat, selectedPos.lon);
                else 
                    return new GeoCoordinate(double.NaN, double.NaN); 
            }
        }
        /// <summary>
        /// Gets the actual selected location in a string form, following the "[lat], [lon]" format.
        /// </summary>
        /// <value>The selected location as a string.</value>
        public string SelectedLocation2String 
        { 
            get
            {
                if(selectedPos != null)
                    return string.Format("{0}, {1}", selectedPos.lat, selectedPos.lon); 
                else
                    return string.Format(", "); 
            } 
        }
        /// <summary>
        /// Gets the latitude of the selected location. Return NaN if no location is selected.
        /// </summary>
        /// <value>The selected latitude.</value>
        public double SelectedLatitude { get { return (selectedPos != null) ? selectedPos.lat : double.NaN; } }
        /// <summary>
        /// Get the longitude of the selected location. Return NaN if no location is selected.
        /// </summary>
        public double SelectedLongitude { get { return (selectedPos != null) ? selectedPos.lon : double.NaN; } }

        /// <summary>
        /// Gets or set the visualization mode of the control.
        /// </summary>
        /// <value>The view mode for the map.</value>
        public ViewType Mode { get; set; }

        /// <summary>
        /// Gets the max level of zoom allowed.
        /// </summary>
        /// <value>The max zoom level.</value>
        public int ZoomMax { get { return zoomMax; } } //nessun set perchè è una caratteristica immutabile della specifica mappa (in questo caso GMM)

        /// <summary>
        /// Gets or sets the zoom level for control.
        /// Only values contained into the [0-ZoomMax] range are allowed.
        /// </summary>
        public int Zoom { 
            get { return zoomLevel; }
            set
            {
                if (zoomLevel != value)
                {
                    //////calcoli le coordinate del centro ((((UGUALE A MouseUp))))
                    double tileRow = (double)(Height / 2 - topLeftOrigin.Y) / tileSize.Height + window.Top;
                    double tileCol = (double)(Width / 2 - topLeftOrigin.X) / tileSize.Width + window.Left;
                    GeoCoordinate geoCenter = GmmCommunicator.TileCoord2GeoCoord(tileRow, tileCol, zoomLevel);

                    //if (selectedPos != null) geoCenter = selectedPos; ////////////////////

                    if (value < 0) zoomLevel = 0;
                    else if (value > zoomMax) zoomLevel = zoomMax;
                    else zoomLevel = value;
                    
                    zoomIndicator.Y = (int)(zoomControlArea.Y + (zoomControlArea.Height / (zoomMax + 1)) * (zoomMax - zoomLevel + 1) - 2);
                
                    window = GMMGetTiles.GmmCommunicator.RemoteGetTiles(imageDir, zoomLevel, (int)mode, geoCenter.lat, geoCenter.lon, window.Height + 1, window.Width + 1); ////////////////////Da evitare, acquisisce tutte le immagini ogni volta

                    for (int i = 0; i <= window.Height; i++)
                        for (int j = 0; j <= window.Width; j++)
                            tiles[i][j] = LocalGetTile(window.Left + j, window.Top + i);
                    
                    CenterMap(geoCenter.lat, geoCenter.lon);
                }
            }
        }
        #endregion

        #region View
        protected override void OnPaintBackground(PaintEventArgs e)
        {
            //base.OnPaintBackground(e);
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);

            Graphics g = e.Graphics;
            if (loaded)
            {
                Bitmap buffer = new Bitmap(Width, Height);
                using (Graphics gBuffered = Graphics.FromImage(buffer))
                {
                    int x, y = topLeftOrigin.Y;
                    for (int i = 0; i < tiles.Length; i++)
                    {
                        x = topLeftOrigin.X;
                        for (int j = 0; j < tiles[0].Length; j++)
                        {
                            gBuffered.DrawImage(tiles[i][j], x, y);
                            //PaintDebug(gBuffered, x, y, i, j);////////////////////DI DEBUG
                            x += tileSize.Width;
                        }
                        y += tileSize.Height;
                    }

                    //Draw center indicator
                    using (Pen red = new Pen(Color.Red))
                    {
                        int xCenter = Width / 2,
                            yCenter = Height / 2;
                        gBuffered.DrawLine(red, xCenter - 3, yCenter, xCenter + 3, yCenter);
                        gBuffered.DrawLine(red, xCenter, yCenter - 3, xCenter, yCenter + 3);
                    }

                    //Drawing of selectedPoint
                    if (selectedPos != null)
                    {
                        TileCoordinate coords = GmmCommunicator.GeoCoord2TileCoord(selectedPos.lat, selectedPos.lon, zoomLevel);
                        x = (int)((coords.column - window.Left) * tileSize.Width + topLeftOrigin.X);
                        y = (int)((coords.row - window.Top) * tileSize.Height + topLeftOrigin.Y);
                        Icon marker = Properties.Resources.pushpin;
                        gBuffered.DrawIcon(marker, x - marker.Width / 2, y - marker.Height);///////////////////
                    }

                    if (showZoomControl) DrawZoomControl(gBuffered);
                    if (showModeControl) DrawModeControl(gBuffered);
                    if (showCentrateControl) gBuffered.DrawImage(Properties.Resources.centrate, centrateControl.X, centrateControl.Y);
                }

                g.DrawImage(buffer, 0, 0);
                buffer.Dispose();
            }
            else g.Clear(BackColor); 

                g.Dispose();
        }

        /// <summary>
        /// Draws on each tile the relative identificative coordinates.
        /// This is only a debug method.
        /// </summary>
        /// <param name="g">The DC to use for drawing.</param>
        /// <param name="x">The left coordinate of the tile.</param>
        /// <param name="y">The top coordinate of the tile.</param>
        /// <param name="i">The latitude identification.</param>
        /// <param name="j">The longitude identification.</param>
        private void PaintDebug(Graphics g, int x, int y, int i, int j)
        {
            using (Pen p = new Pen(Color.Red))
                g.DrawRectangle(p, x, y, 64, 64);
            g.DrawString("C=" + (window.Left + j).ToString(), this.Font, new SolidBrush(Color.Red), x, y + 10);
            g.DrawString("R=" + (window.Top + i).ToString(), this.Font, new SolidBrush(Color.Red), x, y + 30);

        }

        /// <summary>
        /// Draws the zoom controls
        /// </summary>
        /// <param name="g">The DC to use for drawing</param>
        private void DrawZoomControl(Graphics g)
        {
            //Drawing of zoomControl
            using (Pen p = new Pen(Color.Black))
            using (Brush b = new SolidBrush(Color.White))
            {
                //ZoomIn button
                g.FillRectangle(b, zoomIn);
                g.DrawRectangle(p, zoomIn);
                g.DrawLine(p, zoomIn.Left + zoomIn.Width / 2, zoomIn.Top + 3, zoomIn.Left + zoomIn.Width / 2, zoomIn.Bottom - 3);
                g.DrawLine(p, zoomIn.Left + 3, zoomIn.Top + zoomIn.Height / 2, zoomIn.Right - 3, zoomIn.Top + zoomIn.Height / 2);

                //ZoomOut button
                g.FillRectangle(b, zoomOut);
                g.DrawRectangle(p, zoomOut);
                g.DrawLine(p, zoomOut.Left + 3, zoomOut.Top + zoomOut.Height / 2, zoomOut.Right - 3, zoomOut.Top + zoomOut.Height / 2);

                //ZoomLine
                int wNick = zoomControlArea.Width - 2;
                int hNick = 2;
                double range = zoomControlArea.Height / (zoomMax + 1);
                for (int i = 1; i <= zoomMax + 1; i++)
                {
                    g.FillRectangle(b, zoomControlArea.X + 1, (int)(zoomControlArea.Y + range * i - hNick / 2), wNick, hNick);
                    g.DrawRectangle(p, zoomControlArea.X + 1, (int)(zoomControlArea.Y + range * i - hNick / 2), wNick, hNick);
                }

                int xMid = (zoomControlArea.Left + zoomControlArea.Right) / 2;
                g.FillRectangle(b, xMid - 2, zoomControlArea.Y, 4, zoomControlArea.Height);
                g.DrawRectangle(p, xMid - 2, zoomControlArea.Y, 4, zoomControlArea.Height);

                //ZoomBar indicator
                //int y = (int)(zoomControlArea.Y + range * (zoomMax - zoomLevel + 1));
                //g.FillRectangle(b, zoomControlArea.X, y - hNick, zoomControlArea.Width, 2 * hNick);
                //g.DrawRectangle(p, zoomControlArea.X, y - hNick, zoomControlArea.Width, 2 * hNick);
                //g.DrawLine(p, zoomControlArea.Left + 3, y, zoomControlArea.Right - 3, y);
                g.FillRectangle(b, zoomIndicator);
                g.DrawRectangle(p, zoomIndicator);
                int y = zoomIndicator.Y + zoomIndicator.Height/2;
                g.DrawLine(p, zoomIndicator.Left + 3, y, zoomIndicator.Right - 3, y);
            }
        }

        /// <summary>
        /// Draws the mode controls
        /// </summary>
        /// <param name="g">The DC to use for drawing</param>
        private void DrawModeControl(Graphics g)
        {
            using (Pen p = new Pen(Color.Black))
            using (Brush b = new SolidBrush(Color.White))
            {
                p.Width = (mode == ViewType.Map) ? 2 : 1;
                g.FillRectangle(b, mapControlArea);
                g.DrawRectangle(p, mapControlArea);
                Size txtSize = g.MeasureString("Map",Font).ToSize();
                g.DrawString("Map", 
                    new Font(Font.Name, Font.Size, (mode == ViewType.Map) ? FontStyle.Bold : FontStyle.Regular),
                    new SolidBrush(Color.Black), 
                    mapControlArea.X + (mapControlArea.Width - txtSize.Width) / 2, 
                    mapControlArea.Y + (mapControlArea.Height - txtSize.Height) / 2);
                
                p.Width = (mode == ViewType.Satellite) ? 2 : 1;
                g.FillRectangle(b, satControlArea);
                g.DrawRectangle(p, satControlArea);
                txtSize = g.MeasureString("Sat", Font).ToSize();
                g.DrawString("Sat",
                    new Font(Font.Name,Font.Size, (mode == ViewType.Satellite) ? FontStyle.Bold : FontStyle.Regular), 
                    new SolidBrush(Color.Black), 
                    satControlArea.X + (satControlArea.Width - txtSize.Width) / 2, 
                    satControlArea.Y + (satControlArea.Height - txtSize.Height) / 2);
            }
        }
        #endregion

        #region UserInteraction
        /// <summary>
        /// Raises the <see cref="E:System.Windows.Forms.Control.MouseUp"/> event.
        /// </summary>
        /// <param name="e">A <see cref="T:System.Windows.Forms.MouseEventArgs"/> that contains the event data.</param>
        protected override void OnMouseUp(MouseEventArgs e)
        {
            if (loaded)
            {
                if (e.X == clickPos.X && e.Y == clickPos.Y)
                {
                    if (showZoomControl)
                    {
                        if (zoomIn.Contains(e.X, e.Y)) { Zoom = zoomLevel + 1; goto End; }
                        else if (zoomOut.Contains(e.X, e.Y)) { Zoom = zoomLevel - 1; goto End; }
                        else if (zoomControlArea.Contains(e.X, e.Y))
                        {
                            double range = zoomControlArea.Height / (zoomMax + 1);
                            double ratio = (e.Y - zoomControlArea.Y) / range;
                            int zoom = (int)Math.Round(ratio);
                            System.Diagnostics.Debug.WriteLine(string.Format("Richiesto zoom di livello {0}, rapporto {1}", zoom, ratio));
                            Zoom = zoomMax - zoom;

                            goto End;
                        }
                    }
                    if (showModeControl)
                        if (mapControlArea.Contains(e.X, e.Y))
                        {
                            mode = ViewType.Map;
                            RenewTileMatrix();
                            goto End;
                        }
                        else if (satControlArea.Contains(e.X, e.Y))
                        {
                            mode = ViewType.Satellite;
                            RenewTileMatrix();
                            goto End;
                        }
                    if (showCentrateControl && selectedPos != null && centrateControl.Contains(e.X, e.Y))
                    {
                        CenterMap(selectedPos.lat, selectedPos.lon);
                        goto End;
                    }

                    //aggiungi pushpin
                    double tileRow = (double)(e.Y - topLeftOrigin.Y) / tileSize.Height + window.Top;
                    double tileCol = (double)(e.X - topLeftOrigin.X) / tileSize.Width + window.Left;
                    selectedPos = GMMGetTiles.GmmCommunicator.TileCoord2GeoCoord(tileRow, tileCol, zoomLevel);
                    OnSelectedLocationChanged(new EventArgs());
                }
                else if (zoomIndicatorDragging) //dovrebbe bastare, perchè se è settato, allor doveva essere settabile, e quindi zoomControl visibile
                {
                    double range = zoomControlArea.Height / (zoomMax + 1); //betheen two levels, in px
                    double ratio = (zoomIndicator.Y + zoomIndicator.Height / 2 - zoomControlArea.Y) / range -1;
                    int zoom = (int)Math.Round(ratio);
                    System.Diagnostics.Debug.WriteLine(string.Format("Richiesto zoom di livello {0}, rapporto {1}", zoom, ratio));
                    Zoom = zoomMax - zoom;
                    zoomIndicatorDragging = false;
                }

                End:
                Refresh();
            }
    
            base.OnMouseUp(e);
        }

        /// <summary>
        /// Raises the <see cref="E:System.Windows.Forms.Control.MouseDown"/> event.
        /// </summary>
        /// <param name="e">A <see cref="T:System.Windows.Forms.MouseEventArgs"/> that contains the event data.</param>
        protected override void OnMouseDown(MouseEventArgs e)
        {
            if (loaded)
            {
                this.prevDragPosition = new Point(e.X, e.Y);
                this.clickPos = prevDragPosition;
                System.Diagnostics.Debug.WriteLine("mousedown in : " + e.X + ", " + e.Y);
                zoomIndicatorDragging = showZoomControl && zoomIndicator.Contains(clickPos);
            }

            base.OnMouseDown(e);
        }

        /// <summary>
        /// Raises the <see cref="E:System.Windows.Forms.Control.MouseMove"/> event.
        /// </summary>
        /// <param name="e">A <see cref="T:System.Windows.Forms.MouseEventArgs"/> that contains the event data.</param>
        protected override void OnMouseMove(MouseEventArgs e)
        {
            if (loaded)
            {
                if (zoomIndicatorDragging)
                {
                    zoomIndicator.Y += e.Y - prevDragPosition.Y;
                    if (zoomIndicator.Top < zoomControlArea.Top) zoomIndicator.Y = zoomControlArea.Y;
                    else if (zoomIndicator.Bottom > zoomControlArea.Bottom) zoomIndicator.Y = zoomControlArea.Bottom - zoomIndicator.Height;
                    
                    Invalidate(new Rectangle(zoomControlArea.X, zoomControlArea.Y, zoomControlArea.Width+1, zoomControlArea.Height+1));
                    Update();
                }
                else
                {
                    topLeftOrigin.X += e.X - prevDragPosition.X;
                    topLeftOrigin.Y += e.Y - prevDragPosition.Y;

                    RecalculateTiles();

                    Refresh();
                }
                prevDragPosition = new Point(e.X, e.Y);
            }

            base.OnMouseMove(e);
        }
        #endregion

        /// <summary>
        /// Raises the <see cref="E:SelectedLocationChanged"/> event.
        /// </summary>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        protected virtual void OnSelectedLocationChanged(EventArgs e)
        {
            EventHandler handler = SelectedLocationChanged;
            if (handler != null)
            {
                handler(this, e);
            }
        }

        /// <summary>
        /// Renews the tile matrix.
        /// </summary>
        private void RenewTileMatrix()
        {
            double tileRow = (double)(Height / 2 - topLeftOrigin.Y) / tileSize.Height + window.Top;
            double tileCol = (double)(Width / 2 - topLeftOrigin.X) / tileSize.Width + window.Left;
            GeoCoordinate geoCenter = GmmCommunicator.TileCoord2GeoCoord(tileRow, tileCol, zoomLevel);

            window = GMMGetTiles.GmmCommunicator.RemoteGetTiles(imageDir, zoomLevel, (int)mode, geoCenter.lat, geoCenter.lon, window.Height + 1, window.Width + 1); //FIX-ME: Da evitare, acquisisce tutte le immagini ogni volta

            for (int i = 0; i <= window.Height; i++)
                for (int j = 0; j <= window.Width; j++)
                    tiles[i][j] = LocalGetTile(window.Left + j, window.Top + i);

            CenterMap(geoCenter.lat, geoCenter.lon);
        }

        /// <summary>
        /// Recalculates which tiles are visible when a drag introduce a new row or column.
        /// </summary>
        private void RecalculateTiles()
        {
            while (topLeftOrigin.X > 0)
            {
                topLeftOrigin.X -= tileSize.Width;
                AppendCol(GrowingSide.BEFORE); //aggiungi colonna a SX
            }
            while (topLeftOrigin.X + (tileSize.Width * tiles[0].Length) < Width)
            {
                topLeftOrigin.X += tileSize.Width;
                AppendCol(GrowingSide.AFTER); //aggiungi colonna a DX
            }

            while (topLeftOrigin.Y > 0)
            {
                topLeftOrigin.Y -= tileSize.Height;
                AppendRow(GrowingSide.BEFORE); //aggiungi riga a TOP
            }
            while (topLeftOrigin.Y + (tileSize.Height * tiles.Length) < Height)
            {
                topLeftOrigin.Y += tileSize.Height;
                AppendRow(GrowingSide.AFTER); //aggiungi riga a BOTTOM
            }
        }

        /// <summary>
        /// Appends a new column to the specified side, shifting the remaining ones to the other side.
        /// The new column is filled using cached tiles, if available. A request of new tiles is made for all
        /// the adiacent tiles.
        /// </summary>
        /// <param name="side">The side in which append the new column</param>
        private void AppendCol(GrowingSide side)
        {
            int iSide = (int)side;

            window.X += iSide;

            //Requiring-fase
            for (int x = (side == GrowingSide.BEFORE) ? window.Left : window.Right, y1 = window.Top; y1 <= window.Bottom; )
            {
                if (System.IO.File.Exists(string.Format("{0}{1:d2}_{2:d6}_{3:d6}.{4}", imageDir, zoomLevel + 1, x, y1, mode == ViewType.Map ? "PNG" : "JPG")))
                    y1++;
                else if (y1 == window.Bottom)
                {
                    GMMGetTiles.GmmCommunicator.RemoteGetTiles(imageDir, zoomLevel, (int)mode, x, x, y1, y1);
                    break;
                }
                else
                {
                    for (int y2 = y1 + 1; y2 <= window.Bottom; y2++)
                        if (System.IO.File.Exists(string.Format("{0}{1:d2}_{2:d6}_{3:d6}.{4}", imageDir, zoomLevel + 1, x, y2, mode == ViewType.Map ? "PNG" : "JPG")))
                        {
                            GMMGetTiles.GmmCommunicator.RemoteGetTiles(imageDir, zoomLevel, (int)mode, x, x, y1, y2 - 1);
                            y1 = y2 + 1;
                            break;
                        }
                        else if (y2 == window.Bottom)
                        {
                            GMMGetTiles.GmmCommunicator.RemoteGetTiles(imageDir, zoomLevel, (int)mode, x, x, y1, y2);
                            y1 = y2 + 1;
                        }
                }
            }

            //Shifting-fase
            int first = (side == GrowingSide.BEFORE) ? tiles[0].Length - 1 : 0;
            int lastIncr = tiles[0].Length - 1;
            for (int i = 0; i < tiles.Length; i++)
            {
                for (int j = 0; j < lastIncr; j++)
                    tiles[i][first + iSide * j] = tiles[i][first + iSide * (j + 1)];
                tiles[i][first + iSide * lastIncr] =
                    LocalGetTile((side == GrowingSide.BEFORE) ? window.Left : window.Right, window.Top + i);
                //tiles[i][0] = new Bitmap(tileSize.Width, tileSize.Height);
            }
        }

        /// <summary>
        /// Appends a new row to the specified side, shifting the remaining ones to the other side.
        /// The new row is filled using cached tiles, if available. A request of new tiles is made for all
        /// the adiacent tiles.
        /// </summary>
        /// <param name="side">The side in which append the new row</param>
        private void AppendRow(GrowingSide side)
        {
            int iSide = (int)side;

            window.Y += iSide;

            //Requiring-fase
            for (int x1 = window.Left, y = (side == GrowingSide.BEFORE) ? window.Top : window.Bottom; x1 <= window.Right; )
                if (System.IO.File.Exists(string.Format("{0}{1:d2}_{2:d6}_{3:d6}.{4}", imageDir, zoomLevel + 1, x1, y, mode == ViewType.Map ? "PNG" : "JPG")))
                    x1++;
                else if (x1 == window.Right)
                {
                    GMMGetTiles.GmmCommunicator.RemoteGetTiles(imageDir, zoomLevel, (int)mode, x1, x1, y, y);
                    break;
                }
                else
                    for (int x2 = x1 + 1; x2 <= window.Right; x2++)
                        if (System.IO.File.Exists(string.Format("{0}{1:d2}_{2:d6}_{3:d6}.{4}", imageDir, zoomLevel + 1, x2, y, mode == ViewType.Map ? "PNG" : "JPG")))
                        {
                            GMMGetTiles.GmmCommunicator.RemoteGetTiles(imageDir, zoomLevel, (int)mode, x1, x2 - 1, y, y);
                            x1 = x2 + 1;
                            break;
                        }
                        else if (x2 == window.Right)
                        {
                            GMMGetTiles.GmmCommunicator.RemoteGetTiles(imageDir, zoomLevel, (int)mode, x1, x2, y, y);
                            x1 = x2 + 1;
                        }

            //Shifting-fase
            int first = (side == GrowingSide.BEFORE) ? tiles.Length - 1 : 0;
            int lastIncr = tiles.Length - 1;
            for (int i = 0; i < lastIncr; i++)
                for (int j = 0; j < tiles[0].Length; j++)
                    tiles[first + iSide * i][j] = tiles[first + iSide * (i + 1)][j];
            int rowTile = (side == GrowingSide.BEFORE) ? window.Top : window.Bottom;
            int row = first + iSide * (tiles.Length - 1);
            for (int j = 0; j < tiles[0].Length; j++)
                //tiles[0][j] = new Bitmap(tileSize.Width, tileSize.Height);
                tiles[row][j] = LocalGetTile(window.Left + j, rowTile); //
        }

        public new void Dispose()
        {
            GmmCommunicator.AcquiredTile -= acquiringHandler;

            foreach (Image[] row in tiles)
                foreach (Image tile in row)
                    tile.Dispose();
            
            base.Dispose(true);
        }
    }
}